#ifndef AST_Builder_GUARD
#define AST_Builder_GUARD

#include "SPA_Data_Types.h"
#include "AST_XLab2.h"

class AST_Builder {
private:
	AST *_ast;
	//procNode *currProcNode;
	typedef struct AST_State {
		INTEGER numProcs;
		INTEGER numStmts;

		procNode *currProcNode;
		stmtNode *currStmtNode;
		stack<composite_expr*> exprStack;

		enum CONTAINER_STMT_TYPE { CONTAINER_IF, CONTAINER_WHILE };
		stack<stmtNode*> container_stack;
		stack<stmtNode**> newStmtNode_stack;

	} AST_State;
	AST_State _state;

public:
	AST_Builder(){
	};
	AST_Builder( AST *a){
		this->_ast = a;
		_state.currProcNode = NULL;
		_state.numProcs = 0;
		_state.numStmts = 0;
		//memset( _state.newNodeLinks, 0, sizeof(_state.newNodeLinks));
	};

	void addProcedure( PROC_INDEX proc_id ) {
		// add new procedure with
		// index = number of procedures in ast.
		if( _ast->numProcs() >0 ) {
			while( !(_state.container_stack.empty()) )
				_state.container_stack.pop();
			while( !(_state.newStmtNode_stack.empty()) )
				_state.newStmtNode_stack.pop();
		}
		_state.currProcNode = new procNode( _state.numProcs );
		this->_ast->addNewProcedure( _state.currProcNode );
		_state.numProcs++;
		//this->_ast->

		/*_state.currProcNode = _ast->getProcAt(_state.numProcs-1);
		_state.newStmtNode_stack.push (&(_state.currProcNode->_stmtList_Head));
		_state.newStmtNode_stack.push (&(_state.currProcNode->_stmtList_Tail));*/
	};

	void addAssign( VAR_INDEX var_id ) {
		_state.numStmts++;
		_state.currStmtNode = new assignment_stmtNode( _state.numStmts, var_id, _state.currProcNode->getProcId() );
		_state.currStmtNode->_proc_id = _state.currProcNode->getProcId();
		_state.currProcNode->setStmtListTail (_state.currStmtNode);
		if ( !(_state.container_stack.empty())) {
			_state.currStmtNode->links[stmtNode::PARENT] = _state.container_stack.top();
		};
		_ast->addStmt (_state.currStmtNode);
		/*for( int i=0; i<sizeof(_state.newNodeLinks); i++ ) {
		if( _state.newNodeLinks[i] == true) {
		}
		};*/
		//_state.currStmtNode->setLink(stmtNode::FOLLOWS, 
		//_state.currProcNode->addNewStmt( _state.currStmtNode );
		while(!(_state.newStmtNode_stack.empty())) {
			*(_state.newStmtNode_stack.top()) = _state.currStmtNode;
			_state.newStmtNode_stack.pop();
		}
		//_state.newStmtNode_stack.push(&(_state.currStmtNode->_followsNode));
		//_state.newStmtNode_stack.push(&(_state.currStmtNode->_nextNode));
		_state.newStmtNode_stack.push(&(_state.currStmtNode->links[stmtNode::NEXT_STMT]));
		_state.newStmtNode_stack.push(&(_state.currStmtNode->links[stmtNode::FOLLOWS]));
	};

	void beginExpr ( OP_TYPE operation ) {
		OP_TYPE op = operation;
		composite_expr *e;
		switch(op) {
		case OP_Plus:
			e = new composite_expr( OP_Plus );
			break;
		case OP_Minus:
			e = new composite_expr( OP_Minus );
			break;
		case OP_Times:
			e = new composite_expr( OP_Times );
			break;
		default:
			cout << " invalid expression! " << endl;
			break;
		};
		_state.exprStack.push(e);
		// [debugging messages]
		//cout << "begin new expression.." << endl;
		// [/debugging messages]

	};

	void setLeftBranch ( NUMERIC_EXPR_TYPE net, INDEX id ) {
		if( net == NUMERIC_EXPR_TYPE_VARIABLE ) {
			((composite_expr*)_state.exprStack.top())->setLHS (new numeric_expr( id ));
		} else 
			((composite_expr*)_state.exprStack.top())->setLHS (new numeric_expr( id ));
	};

	void setRightBranch ( NUMERIC_EXPR_TYPE net, INDEX id ) {
		if( net == NUMERIC_EXPR_TYPE_VARIABLE ) {
			((composite_expr*)_state.exprStack.top())->setRHS (new numeric_expr( id ));
		} else 
			((composite_expr*)_state.exprStack.top())->setRHS (new numeric_expr( id ));
	};

	void addNumericExpr( NUMERIC_EXPR_TYPE net, INDEX id ) {
		NUMERIC_EXPR_TYPE et = net;
		switch (et) {
		case NUMERIC_EXPR_TYPE_VARIABLE:
			((assignment_stmtNode*)_state.currProcNode->stmtList_Tail())->setRHS( new numeric_expr( id ) );
			break;
		case NUMERIC_EXPR_TYPE_CONSTANT:
			((assignment_stmtNode*)_state.currProcNode->stmtList_Tail())->setRHS( new numeric_expr( id ) );
			break;
		default:
			break;
		};
	};

	expr* endExpr( BOOLEAN b) {
		if( _state.exprStack.empty() 
			|| ((composite_expr*)_state.exprStack.top())->getLHS() == NULL 
			|| ((composite_expr*)_state.exprStack.top())->getRHS() == NULL ) {
				cout << "invalid expression!" << endl;
				return NULL;
		} else {// [debugging messages]
			//cout << "subexpression complete: stack size: " << _state.exprStack.size() << endl;
			// [debugging messages]
			expr *top_of_stack_expr = _state.exprStack.top();
			_state.exprStack.pop();
			if( _state.exprStack.size() == 0) {
				//cout << "end of expression" << " stack size: " << _state.exprStack.size() << endl;
				((assignment_stmtNode*)_state.currProcNode->stmtList_Tail())->setRHS(top_of_stack_expr);
			}else {
				if ( b == composite_expr::ADD_TO_LHS ) {
					_state.exprStack.top()->setLHS(top_of_stack_expr);
				}else {
					_state.exprStack.top()->setRHS(top_of_stack_expr);
				}
			}
			return top_of_stack_expr;
		}
	};

	void addCall( PROC_INDEX proc_id ) {
		_state.numStmts++;
		_state.currStmtNode = new call_stmtNode(_state.numStmts, proc_id, _state.currProcNode->getProcId());
		_state.currStmtNode->_proc_id = _state.currProcNode->getProcId();
		_state.currProcNode->setStmtListTail (_state.currStmtNode);
		if ( !(_state.container_stack.empty())) {
			_state.currStmtNode->links[stmtNode::PARENT] = _state.container_stack.top();
		};
		_ast->addStmt (_state.currStmtNode);

		while(!(_state.newStmtNode_stack.empty())) {
			*(_state.newStmtNode_stack.top()) = _state.currStmtNode;
			_state.newStmtNode_stack.pop();
		}
		_state.newStmtNode_stack.push (&(_state.currStmtNode->links[stmtNode::NEXT_STMT]));
		_state.newStmtNode_stack.push (&(_state.currStmtNode->links[stmtNode::FOLLOWS]));

	}
	
	void beginIf( VAR_INDEX condition_variable ) {
		//cout << "\n: beginIF" << endl;
		_state.numStmts++;
		_state.currStmtNode = new if_stmtNode( _state.numStmts, condition_variable, _state.currProcNode->getProcId());
		_state.currStmtNode->_proc_id = _state.currProcNode->getProcId();
		_state.currProcNode->setStmtListTail (_state.currStmtNode);
		if ( !(_state.container_stack.empty())) {
			_state.currStmtNode->links[stmtNode::PARENT] = _state.container_stack.top();
		};
		_ast->addStmt (_state.currStmtNode);

		while(!(_state.newStmtNode_stack.empty())) {
			*(_state.newStmtNode_stack.top()) = _state.currStmtNode;
			_state.newStmtNode_stack.pop();
		}
		_state.container_stack.push(_state.currStmtNode);
		_state.newStmtNode_stack.push (&(_state.currStmtNode->links[stmtNode::NEXT_TO_THEN]));
	};

	void endIf( if_stmtNode::IF_BLOCK_TYPE blk_type ) {
		//cout << "\n: endIf: "<< blk_type << endl;
		if( !(_state.container_stack.empty()) 
			&& _state.container_stack.top()->getStmtType() == STATEMENT_If) {
				switch (blk_type) {
				case if_stmtNode::THEN_BLOCK:
					if (_state.container_stack.top()->links[stmtNode::NEXT_TO_THEN] != NULL ) {
						while (!(_state.newStmtNode_stack.empty()))
							_state.newStmtNode_stack.pop();

						_state.newStmtNode_stack.push (&(_state.container_stack.top()->links[stmtNode::NEXT_TO_ELSE]));
						//cout << "Set If Then stmt block tail\n" ;
						((if_stmtNode*)_state.container_stack.top())->addr_thenStmtListTail = &(_state.currStmtNode->links[stmtNode::NEXT_STMT]);
					} else ((if_stmtNode*)_state.container_stack.top())->addr_thenStmtListTail = NULL;
					break;

				case if_stmtNode::ELSE_BLOCK:
					if (_state.container_stack.top()->links[stmtNode::NEXT_TO_ELSE] != NULL ) {
						while (!(_state.newStmtNode_stack.empty()))
							_state.newStmtNode_stack.pop();
						if( ((if_stmtNode*)(_state.container_stack.top()))->addr_thenStmtListTail != NULL ) {
							//cout << "linking if then";
							_state.newStmtNode_stack.push (((if_stmtNode*)_state.container_stack.top())->addr_thenStmtListTail);
						}
						_state.newStmtNode_stack.push (&(_state.currStmtNode->links[stmtNode::NEXT_STMT]));
						_state.newStmtNode_stack.push (&(_state.container_stack.top()->links[stmtNode::FOLLOWS]));
					}
					_state.container_stack.pop();
					break;

				default:
					cout << ": invalid end of If statement block" << endl;
					break;
				}
				//_state.currStmtNode = _state.container_stack.top()->_nextNode;
		}else {
			cout << "Invalid If block end" << endl;
		}
	};

	void beginWhile( VAR_INDEX condition_variable ) {
		//cout << "\n: beginWhlie" << endl;
		_state.numStmts++;
		_state.currStmtNode = new while_stmtNode( _state.numStmts, condition_variable, _state.currProcNode->getProcId());
		_state.currStmtNode->_proc_id = _state.currProcNode->getProcId();
		_state.currProcNode->setStmtListTail (_state.currStmtNode);
		if ( !(_state.container_stack.empty())) {
			_state.currStmtNode->links[stmtNode::PARENT] = _state.container_stack.top();
		};
		_ast->addStmt (_state.currStmtNode);

		while(!(_state.newStmtNode_stack.empty())) {
			*(_state.newStmtNode_stack.top()) = _state.currStmtNode;
			_state.newStmtNode_stack.pop();
		}
		_state.container_stack.push(_state.currStmtNode);
		//_state.newStmtNode_stack.push (&(_state.currStmtNode->links[stmtNode::NEXT_STMT]));
		_state.newStmtNode_stack.push (&(_state.currStmtNode->links[stmtNode::NEXT_TO_WHILE]));
	};

	void endWhile() {
		//cout << "\n: endWhile()" << endl;
		if( !(_state.container_stack.empty()) 
			&& _state.container_stack.top()->getStmtType() == STATEMENT_While) {
				if( _state.container_stack.top()->links[stmtNode::NEXT_TO_WHILE] != NULL ) {
					while (!(_state.newStmtNode_stack.empty())) {
						_state.newStmtNode_stack.pop();
					}
					_state.newStmtNode_stack.push (&(_state.container_stack.top()->links[stmtNode::FOLLOWS]));
					_state.newStmtNode_stack.push (&(_state.container_stack.top()->links[stmtNode::NEXT_STMT]));
					_state.newStmtNode_stack.push (&(_state.currStmtNode->links[stmtNode::NEXT_STMT]));
				}else {
					_state.newStmtNode_stack.push (
						&(_state.container_stack.top()->links[stmtNode::FOLLOWS]));
					_state.newStmtNode_stack.push (
						&(_state.container_stack.top()->links[stmtNode::FOLLOWS]));
				}
				//_state.currStmtNode = _state.container_stack.top()->_nextNode;
				_state.container_stack.pop();
		}else {
			cout << "Invalid While block end" << endl;
		}
	};


	// additional functions
	void listAllStmts();
};

#endif